<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" >

<title>依赖注入作用域(Scope)的测试 | 没想好的个人博客</title>

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

<!-- <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> -->
   <!-- Font Awesome CSS-->
   <link href="https://cdn.bootcss.com/font-awesome/5.13.0/css/all.css" rel="stylesheet">

   <link rel="stylesheet" href="https://cdn.bootcss.com/font-awesome/5.7.2/css/all.min.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">

<link rel="shortcut icon" href="https://shixixiyue.gitee.io//favicon.ico?v=1709791195950">
<link rel="stylesheet" href="https://shixixiyue.gitee.io//styles/main.css">



<link rel="stylesheet" href="https://unpkg.com/aos@next/dist/aos.css" />
<script src="https://jsd.onmicrosoft.cn/npm/vue/dist/vue.js"></script>


<style>
  .hljs-ln{
    padding-bottom: 1.2rem;
    margin: 0rem !important;
  }
  .hljs-ln td {
    border: none !important;
    padding: 2px 6px !important;
    line-height: 1.2rem;
    font-size: 1rem;
  }
  .hljs-ln tr td:first-child  {
    color: cadetblue;
    border-right: 1px solid !important;
  }
  .hljs-ln tr {
    border: none !important;
  }
</style>

    <meta name="description" content="开发时遇到了这个情况，我想在两个业务类中传输数据，即类1定义了日志的实现，类2调用，所以我想将日志实现作为一个依赖注入，使用 Scoped 在本次提交时生效，然而我的业务代码是 Singleton 注入 services.AddSingle..." />
    <meta name="keywords" content=".Net Core" />
  </head>
  <body>
    <div id="app" class="main">

      <div class="sidebar" :class="{ 'full-height': menuVisible }">
  <div class="top-container" data-aos="fade-right">
    <div class="top-header-container">
      <a class="site-title-container" href="https://shixixiyue.gitee.io/">
        <img src="https://shixixiyue.gitee.io//images/avatar.png?v=1709791195950" class="site-logo">
        <h1 class="site-title">没想好的个人博客</h1>
      </a>
      <div class="menu-btn" @click="menuVisible = !menuVisible">
        <div class="line"></div>
      </div>
    </div>
    <div>
      
        
          <a href="https://shixixiyue.gitee.io/" class="site-nav">
            首页
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/4gOKry8Qv/" class="site-nav">
            .Net Core
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/njETBpqXu/" class="site-nav">
            FineUICore
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/XyXyeLOBB" class="site-nav">
            读书笔记
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/TyVX6hfCJ" class="site-nav">
            Vue笔记
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/3Hz2sPtzO/" class="site-nav">
            产品
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/Db1wShB_E" class="site-nav">
            其他
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io/" class="site-nav">
            一一一一一一一一一
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//archives" class="site-nav">
            文章归档
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tags" class="site-nav">
            全部标签
          </a>
        
      
    </div>
  </div>
  <div class="bottom-container" data-aos="flip-up" data-aos-offset="0">
    <div class="social-container">
      
        
      
        
      
        
      
        
      
        
      
    </div>
    <div class="site-description">
      没事啥都说
    </div>
    <div class="site-footer">
      QQ：<a href="tencent://message/?uin=935732994&Site=''&Menu=yes">935732994<a/> <a href="http://beian.miit.gov.cn">京ICP备2024045283号<a/> | <a class="rss" href="https://shixixiyue.gitee.io//atom.xml" target="_blank">RSS</a>
    </div>
  </div>
</div>


      <div class="main-container">
        <div class="content-container" data-aos="fade-up">
          <div class="post-detail">
            <h2 class="post-title">依赖注入作用域(Scope)的测试</h2>
            <div class="post-date">2023-08-10
            
                
                  <a href="https://shixixiyue.gitee.io/tag/4gOKry8Qv/" class="tag">
                    .Net Core
                  </a>
                
            </div>
            
              <div class="feature-container" style="background-image: url('https://shixixiyue.gitee.io//post-images/yi-lai-zhu-ru-zuo-yong-yu-scopede-ce-shi.png')">
              </div>
            
            <div class="post-content" v-pre>
              <p>开发时遇到了这个情况，我想在两个业务类中传输数据，即类1定义了日志的实现，类2调用，所以我想将日志实现作为一个依赖注入，使用 Scoped 在本次提交时生效，然而我的业务代码是 Singleton 注入 <code>services.AddSingleton</code> ，当业务类直接通过构造引用区域依赖 Scoped 时会报错，稍微测试了一下，这里记录总结；</p>
<!-- more -->
<p><ul class="markdownIt-TOC">
<li>
<ul>
<li>
<ul>
<li>
<ul>
<li><a href="#1-%E7%8E%AF%E5%A2%83%E5%87%86%E5%A4%87-%E8%BF%98%E5%8E%9F%E9%94%99%E8%AF%AF">1. 环境准备 还原错误</a></li>
<li><a href="#2-%E7%AC%AC%E4%B8%80%E6%AC%A1%E5%B0%9D%E8%AF%95">2. 第一次尝试</a></li>
<li><a href="#3-%E7%AC%AC%E4%BA%8C%E6%AC%A1%E5%B0%9D%E8%AF%95">3. 第二次尝试</a></li>
<li><a href="#4-%E7%AC%AC%E4%B8%89%E6%AC%A1%E5%B0%9D%E8%AF%95">4. 第三次尝试</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</p>
<h4 id="1-环境准备-还原错误">1. 环境准备 还原错误</h4>
<p>1.1 新建 一个消息类 <code>ScopeInfo</code> , 两个业务类 <code>Class1</code> 和 <code>Class2</code></p>
<pre><code>namespace ScopeTest
{
    public class ScopeInfo
    {
        public int Count { get; set; } = 0;
    }
}
</code></pre>
<pre><code>namespace ScopeTest
{
    public class Class1
    {
        private ScopeInfo info;

        public Class1(ScopeInfo info)
        {
            this.info = info;
        }

        public Class1 SetValue()
        {
            this.info.Count++;
            Console.WriteLine($&quot;{nameof(Class1)}:{info?.Count}&quot;);
            return this;
        }
    }
}
</code></pre>
<pre><code>namespace ScopeTest
{
    public class Class2
    {
        private ScopeInfo info;

        public Class2(ScopeInfo info)
        {
            this.info = info;
        }

        public Class2 GetValue()
        {
            Console.WriteLine($&quot;{nameof(Class2)}:{info?.Count}&quot;);
            return this;
        }
    }
}
</code></pre>
<p>1.2 注册注入</p>
<pre><code>builder.Services.AddSingleton&lt;Class1&gt;();
builder.Services.AddSingleton&lt;Class2&gt;();
builder.Services.AddScoped&lt;ScopeInfo&gt;();
</code></pre>
<p>1.3 在方法中使用</p>
<pre><code>public WeatherForecastController(Class1 class1, Class2 class2)
{
	this.class1 = class1;
	this.class2 = class2;
}

[HttpGet(Name = &quot;GetWeatherForecast&quot;)]
public IEnumerable&lt;WeatherForecast&gt; Get()
{
	class1.SetValue();
	class2.GetValue();
	...略
}
</code></pre>
<p>1.4 运行报错<br>
运行后不能启动，报错 ❗️</p>
<pre><code>System.AggregateException:“Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: ScopeTest.Class1 Lifetime: Singleton ImplementationType: ScopeTest.Class1': Cannot consume scoped service 'ScopeTest.ScopeInfo' from singleton 'ScopeTest.Class1'.) (Error while validating the service descriptor 'ServiceType: ScopeTest.Class2 Lifetime: Singleton ImplementationType: ScopeTest.Class2': Cannot consume scoped service 'ScopeTest.ScopeInfo' from singleton 'ScopeTest.Class2'.)”
</code></pre>
<p>翻译过来就一句话 <code>无法从单例 &quot;ScopeTest.Class1 &quot;消费作用域服务 &quot;ScopeTest.ScopeInfo&quot;</code></p>
<p>因为 <code>Class1</code> 和 <code>Class2</code> 都是全局单例的，所以一开始就会实例，而 <code>ScopeInfo</code> 提交时才实例，所以拿不到；</p>
<h4 id="2-第一次尝试">2. 第一次尝试</h4>
<p>聪明的小伙伴😑已经想到了，可以从 <code>IServiceProvider</code> 中获取注入，而不是构造，将 <code>Class1</code> 和 <code>Class2</code> 改造</p>
<pre><code>namespace ScopeTest
{
    public class Class1
    {
        private ScopeInfo info;
        private readonly IServiceProvider applicationServices;

        public Class1(IServiceProvider applicationServices)
        {
            //第一次尝试
            this.applicationServices = applicationServices;
            //从IServiceProvider 中拿
            info = applicationServices.GetService&lt;ScopeInfo&gt;();
        }

        public Class1 SetValue()
        {
            this.info.Count++;
            Console.WriteLine($&quot;{nameof(Class1)}:{info?.Count}&quot;);
            return this;
        }
    }
}
</code></pre>
<pre><code>namespace ScopeTest
{
    public class Class2
    {
        private ScopeInfo info;
        private readonly IServiceProvider applicationServices;

        public Class2(IServiceProvider applicationServices)
        {
            //第一次尝试
            this.applicationServices = applicationServices;
             //从IServiceProvider 中拿
            info = applicationServices.GetService&lt;ScopeInfo&gt;();
        }

        public Class2 GetValue()
        {
            Console.WriteLine($&quot;{nameof(Class2)}:{info?.Count}&quot;);
            return this;
        }
    }
}
</code></pre>
<p>这次可以运行启动了，我们来触发一次访问，<br>
报错了 ❗️</p>
<pre><code>System.InvalidOperationException:“Cannot resolve scoped service 'ScopeTest.ScopeInfo' from root provider.”
</code></pre>
<p>翻译过来 <code>无法从根提供程序解析作用域服务 &quot;ScopeTest.ScopeInfo&quot;</code></p>
<p>2.1 这里需要创建区域<br>
使用 <code> CreateScope()</code> 可以解决这个问题，写成这样</p>
<pre><code> info = applicationServices.CreateScope().ServiceProvider.GetService&lt;ScopeInfo&gt;();
</code></pre>
<p>到这提交终于不报错了 ，输出如下</p>
<pre><code>Class1:1
Class2:0
</code></pre>
<p>很明显 <code>Class1</code> 的赋值 没有传入到 <code>Class2</code> 中🤔，因为在构造时区域是 <code>Create</code> 出来的，每次 <code>CreateScope()</code> 都是新的区域，所以  <code>Class1</code> 和 <code>Class2</code> 是两个实例</p>
<h4 id="3-第二次尝试">3. 第二次尝试</h4>
<p>这时我又想使用第三个类来 <code>CreateScope</code> 并且赋值到一个变量，这样区域就一个了，但是这个中间类 <code>Middle</code> 其实和 <code>ScopeInfo</code> 类的生命周期是一样的，如果使用 <code>AddSingleton</code> 会全局实例化一次，当第二次提交时不实例化，<code>ScopeInfo</code> 会保留上次的值，这样不行；如果使用 <code>AddScoped</code> 注入，和 <code>ScopeInfo</code> 一样 <code>Class1</code> 和 <code>Class2</code> 还是无法获取到区域实例；</p>
<h4 id="4-第三次尝试">4. 第三次尝试</h4>
<p>再来捋一下需求，我希望 类<code>ScopeInfo</code> 在请求开始实例化一次，之后的所有类中都是这个实例，请求结束销毁，这个类是个什么呢；其实就是当前的上下文 <code>HttpContext</code> ，而且 <code>HttpContext</code> 本来也专门干这个事 <code>HttpContext.Items</code>😲</p>
<p>我已经使用 <code>AddScoped&lt;ScopeInfo&gt;();</code> 了，所以我肯定不会再手动 <code>HttpContext.Items[&quot;ScopeInfo&quot;] =new ScopeInfo();</code> 放一次，也不会出现 <code>new ScopeInfo()</code> 的写法，所以这里放进去的不应该是 <code>ScopeInfo</code> ，而是当前区域，即 提交时，我将 <code>CreateScope</code> 放到 <code>HttpContext.Items</code> 中，这样，所有的类中的区域就一样了，而且提交结束后会随 <code>HttpContext</code> 释放；</p>
<p>问题来了，怎么在提交时 将 一个类 或者 参数 放到 <code>HttpContext.Items</code>  中呢，这样写</p>
<pre><code>//这里需要注入 IHttpContextAccessor
builder.Services.AddHttpContextAccessor();
app.Use(async (context, next) =&gt;
{
    context.Items[&quot;SharedScope&quot;] = app.Services.CreateScope();
    await next();
});
</code></pre>
<p>将 <code>Class1</code> 和 <code>Class2</code> 改造</p>
<pre><code>namespace ScopeTest
{
    public class Class1
    {
        //不要在构造柱中实例了，直接拿
        private ScopeInfo info =&gt; (httpContext.HttpContext.Items[&quot;SharedScope&quot;] as IServiceScope).ServiceProvider.GetService&lt;ScopeInfo&gt;();
        //private readonly IServiceProvider applicationServices;
        //当前上线文
        private readonly IHttpContextAccessor httpContext;

        public Class1(IServiceProvider applicationServices, IHttpContextAccessor httpContext)
        {
            //第一次尝试
            //this.applicationServices = applicationServices;
            //info = applicationServices.GetService&lt;ScopeInfo&gt;();
            this.httpContext = httpContext;
        }

        public Class1 SetValue()
        {
            this.info.Count++;
            Console.WriteLine($&quot;{nameof(Class1)}:{info?.Count}&quot;);
            return this;
        }
    }
}
</code></pre>
<pre><code>namespace ScopeTest
{
    public class Class2
    {
        //不要在构造柱中实例了，直接拿
        private ScopeInfo info =&gt; (httpContext.HttpContext.Items[&quot;SharedScope&quot;] as IServiceScope).ServiceProvider.GetService&lt;ScopeInfo&gt;();
        //private readonly IServiceProvider applicationServices;
        //当前上线文
        private readonly IHttpContextAccessor httpContext;

        public Class2(IServiceProvider applicationServices, IHttpContextAccessor httpContext)
        {
            //第一次尝试
            //this.applicationServices = applicationServices;
            //info = applicationServices.GetService&lt;ScopeInfo&gt;();
            this.httpContext = httpContext;
        }

        public Class2 GetValue()
        {
            Console.WriteLine($&quot;{nameof(Class2)}:{info?.Count}&quot;);
            Console.WriteLine($&quot;================================&quot;);
            return this;
        }
    }
}
</code></pre>
<p>这时 再次提交测试 输出如下</p>
<pre><code>Class1:1
Class2:1
================================
Class1:1
Class2:1
================================
</code></pre>
<p>可以看到值被共享了，而且两次提交 值被重置，😬</p>
<p>4.1 封装<br>
<code>HttpContext.Items</code> 很难看，如果是强类型，可以使用 <code>HttpContext.Features.Set</code> ；创建一个类 <code>SharedScope</code> 和 <code>HttpContext</code> 的扩展方法</p>
<pre><code>using Microsoft.AspNetCore.Mvc.RazorPages;

namespace ScopeTestWebAPP.ScopeTest
{
    public class SharedScope
    {
        public IServiceScope? sharedScope;

        public SharedScope(IApplicationBuilder app)
        {
            sharedScope = app?.ApplicationServices?.CreateScope();
        }
    }

    public static class HttpContextExtensions
    {
        /// &lt;summary&gt;
		/// 得到服务从上下文共享的Scope中，
		/// &lt;/summary&gt;
		/// &lt;typeparam name=&quot;T&quot;&gt;&lt;/typeparam&gt;
		/// &lt;returns&gt;&lt;/returns&gt;
		public static T GetServerScope&lt;T&gt;(this HttpContext context)
        {
            var share = context.Features.Get&lt;SharedScope&gt;();
            return share.sharedScope.ServiceProvider.GetService&lt;T&gt;();
        }
    }
}

</code></pre>
<p>在注册时可以这样写</p>
<pre><code>app.Use(async (context, next) =&gt;
{
    context.Features.Set(new SharedScope(app));
    await next();
});
</code></pre>
<p>使用时可以这样</p>
<pre><code>using ScopeTestWebAPP.ScopeTest;

namespace ScopeTest
{
    public class Class1
    {
        private ScopeInfo info =&gt; httpContext.HttpContext.GetServerScope&lt;ScopeInfo&gt;();
        //当前上线文
        private readonly IHttpContextAccessor httpContext;
        public Class1(IHttpContextAccessor httpContext)
        {
            this.httpContext = httpContext;
        }
    }
}
</code></pre>
<p><code>IHttpContextAccessor</code> 也不想写，可以写到静态类里，其实项目中用是这样的</p>
<pre><code>namespace ScopeTest
{
   public class Class1
   {
      private ScopeInfo info =&gt; 
           FineUICore.PageContext.Current.GetServerScope&lt;ScopeInfo&gt;();
       //又是FineUICore 😐
       public Class1()
       {
       }
   }
}
</code></pre>
<p>另外再异步应将 <code>sharedScope</code> 套上 <code>AsyncLocal</code>，这部分我倒没有测试</p>

            </div>
            
              <div class="tag-container">
                
                  <a href="https://shixixiyue.gitee.io/tag/4gOKry8Qv/" class="tag">
                    .Net Core
                  </a>
                
              </div>
            
            
              <div class="next-post">
                <div class="next">下一篇</div>
                <a href="https://shixixiyue.gitee.io/ling-yu-qu-dong-she-ji-ruan-jian-he-xin-fu-za-xing-ying-dui-zhi-dao-xiu-ding-ban-yi-bu-tu-shu-chu-pin-yi/">
                  <h3 class="post-title">
                    领域驱动设计 软件核心复杂性应对之道 修订版（异步图书出品）（一）
                  </h3>
                </a>
              </div>
            

            

          </div>

        </div>
      </div>
    </div>

    <script src="https://unpkg.com/aos@next/dist/aos.js"></script>
<script type="application/javascript">

AOS.init();

var app = new Vue({
  el: '#app',
  data: {
    menuVisible: false,
  },
})

</script>


  <script src="//jsd.onmicrosoft.cn/gh/highlightjs/cdn-release@11.5.1/build/highlight.min.js"></script>
  <script>
    hljs.initHighlightingOnLoad();
    //hljs.registerLanguage('mermaid', window.hljsDefineMermaid);
  </script>
  <script src="//jsd.onmicrosoft.cn/npm/highlightjs-line-numbers.js@2.8.0/dist/highlightjs-line-numbers.min.js"></script>
  <script>
    hljs.initLineNumbersOnLoad({
      singleLine: true
    });
  </script>
 <script src="https://jsd.onmicrosoft.cn/npm/mermaid/dist/mermaid.min.js"></script>
   <script>
    mermaid.initialize({
      startOnLoad: true,
      securityLevel: 'loose',
    });
  </script>





  </body>
</html>
